package samples;

import its.model.ModelException;
import its.model.environment.Environment;
import its.model.environment.crossroad.Crossroad;
import its.model.environment.crossroad.Program;
import its.opt.OptSimConf;
import its.opt.StatsLoader;
import its.opt.program.Evaluator;
import its.opt.program.ProgramBuilder;
import its.opt.structures.EvolutionParams;
import its.opt.utils.EvaluationFunctionFactory;
import its.opt.utils.LoggingUtils;
import its.opt.utils.OptimizationEvaluatorUtils;
import its.sim.Simulator;
import its.sim.SimulatorConfiguration;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import samples.objectivefunctions.ProgramTrafficAmount;
import engine.Algorithm;
import engine.CachedObjectiveFunction;
import engine.Population;
import engine.PopulationEvaluator;
import engine.SingleThreadedEvaluator;
import engine.exitcriteria.AllIndividualsAreSimilar;
import engine.exitcriteria.MaxIterations;
import engine.exitcriteria.NoImprovementTerminationCondition;
import engine.individuals.ProgramedTrafficNetIndividual;
import engine.operators.ChartReportingMinAvgMaxOperator;
import engine.operators.CombineOperators;
import engine.operators.CombineOperatorsWithEvaluation;
import engine.operators.ReportStatisticsOperator;
import engine.operators.TournamentSelectionOperator;
import engine.operators.WorstHalfCutoff;
import engine.operators.binary.TrafficMutation;
import engine.operators.binary.UniformCrossover;
import engine.utils.ListUtils;

public class ItsSGA {

	/** Logger for this class. */
	private static final Logger LOGGER = Logger.getLogger(ItsSGA.class.getName());

	/** Number of iterations. */
	private static int MAX_ITER = 100;

	/** Size of the population. */
	private static int POPULATION_SIZE = 50;

	private static SimulatorConfiguration conf;

	/**
	 * Entry point to the program.
	 * 
	 * @param args
	 * @throws IOException
	 * @throws ModelException
	 */
	@SuppressWarnings("unchecked")
	public static void main(String[] args) throws ModelException, IOException {

		Date d1 = new Date();
		conf = new SimulatorConfiguration(args);

		if (LOGGER.isLoggable(Level.INFO)) {
			LOGGER.info(LoggingUtils.makeLogStringForAlgorithmParameters(args, conf));
		}

		StatsLoader init_loader = null;
		try {
			init_loader = runSimulation();
		} catch (Exception e) {
			LOGGER.severe("Error during simulation run\n" + e);
			throw new IllegalStateException(e);
		}

		// SimulatorFiles simulationFiles =
		// new SimulatorFiles(new SimulatorConfiguration(args).getConfigFile());

		Evaluator evaluator = 
			EvaluationFunctionFactory.getEvaluator(
					args,
					conf,
					init_loader);

		EvolutionParams params = prepareParams();

		Population<ProgramedTrafficNetIndividual> population = new Population<ProgramedTrafficNetIndividual>(
				generateListOfRandomProgramsNetIndividuals(params));
		
		if (LOGGER.isLoggable(Level.INFO)) {
			LOGGER.info(LoggingUtils.makeLogStringForAlgorithmParameters(params));
		}

		Algorithm<ProgramedTrafficNetIndividual> alg = 
			new Algorithm<ProgramedTrafficNetIndividual>(
				population);

		ProgramTrafficAmount<ProgramedTrafficNetIndividual> programTrafficAmount = 
			new ProgramTrafficAmount<ProgramedTrafficNetIndividual>(
				evaluator);

		CachedObjectiveFunction<ProgramedTrafficNetIndividual> objectiveFunctionWrapper = 
			new CachedObjectiveFunction<ProgramedTrafficNetIndividual>(
				programTrafficAmount);

		ChartReportingMinAvgMaxOperator<ProgramedTrafficNetIndividual> chartReportingOperator = 
			new ChartReportingMinAvgMaxOperator<ProgramedTrafficNetIndividual>(
				"sga",
				conf.getConfigFile().toString(),
				objectiveFunctionWrapper);

		alg.addExitPoint(
				new MaxIterations<ProgramedTrafficNetIndividual>(params.maxGenerations));
		alg.addExitPoint(
				new AllIndividualsAreSimilar<ProgramedTrafficNetIndividual>(objectiveFunctionWrapper));
		alg.addExitPoint(
				new NoImprovementTerminationCondition<ProgramedTrafficNetIndividual>(
						objectiveFunctionWrapper));
		
		CombineOperators<ProgramedTrafficNetIndividual> mainLogic =
			new CombineOperators<ProgramedTrafficNetIndividual>(
					new TournamentSelectionOperator<ProgramedTrafficNetIndividual>(objectiveFunctionWrapper, params),
					new UniformCrossover(),
					new TrafficMutation(params.mutationProbability));
		
		PopulationEvaluator<ProgramedTrafficNetIndividual> popEvaluator = 
			buildObjectiveFunctions(objectiveFunctionWrapper);
		
		alg.addOperator(new CombineOperatorsWithEvaluation<ProgramedTrafficNetIndividual>(
				mainLogic,
				popEvaluator,
				new WorstHalfCutoff<ProgramedTrafficNetIndividual>(objectiveFunctionWrapper)));
		
//		alg.addEvaluationPoint(popEvaluator);

		ReportStatisticsOperator<ProgramedTrafficNetIndividual> reporter = 
			new ReportStatisticsOperator<ProgramedTrafficNetIndividual>(objectiveFunctionWrapper);
		alg.addOperator(reporter);
		alg.addOperator(chartReportingOperator);

		popEvaluator.apply(population);
		reporter.apply(population);
		chartReportingOperator.apply(population);
		alg.run();

		chartReportingOperator.drawStatistics();

		if (LOGGER.isLoggable(Level.INFO)) {
			LOGGER.info("Good rated individuals: " 
					+ programTrafficAmount.getGoodIndividuals());
			Date d2 = new Date();
			LOGGER.info(new Long((d2.getTime() - d1.getTime()) / 1000L).toString());
		}

		// save best individual
		OptimizationEvaluatorUtils.saveBestIndividual(args, alg,
				objectiveFunctionWrapper);
	}

	private static EvolutionParams prepareParams() {
		EvolutionParams params = new EvolutionParams();

		String populationSize = System.getProperty("population.size");
		if (populationSize != null && populationSize.trim().length() > 0) {
			POPULATION_SIZE = Integer.parseInt(populationSize);
		}
		String maxGenerations = System.getProperty("max.generations");
		if (maxGenerations != null && maxGenerations.trim().length() > 0) {
			MAX_ITER = Integer.parseInt(maxGenerations);
		}
//		int percentOffspring = 50;
//		String percentOffspringStr = System.getProperty("percent.offspring");
//		if (percentOffspringStr != null && percentOffspringStr.trim().length() > 0) {
//			percentOffspring = Integer.parseInt(percentOffspringStr);
//		}
		double mutationProb = 0.3d;
		String mutationProbStr = System.getProperty("mutation.probability");
		if (mutationProbStr != null && mutationProbStr.trim().length() > 0) {
			mutationProb = Double.parseDouble(mutationProbStr);
		}
		int tournamentSize = 5;
		String tournamentSizeStr = System.getProperty("tournament.size");
		if (tournamentSizeStr != null && tournamentSizeStr.trim().length() > 0) {
			tournamentSize = Integer.parseInt(tournamentSizeStr);
		}
		
		params.N = POPULATION_SIZE;
		// defined elsewhere
//		params.n = VECTOR_SIZE;
		params.maxGenerations = MAX_ITER;
		// params.randSeed = 123456L;
		params.tournamentSize = tournamentSize;
		params.mutationProbability = mutationProb;

		return params;
	}

	/**
	 * Creates a list of objective functions (one in this case) to be optimized.
	 * 
	 * @param objectiveFunctionWrapper
	 *          A function that the list is based on.
	 * @return List of objective functions.
	 */
	@SuppressWarnings("unchecked")
	private static PopulationEvaluator<ProgramedTrafficNetIndividual> buildObjectiveFunctions(
			CachedObjectiveFunction<ProgramedTrafficNetIndividual> objectiveFunctionWrapper) {
		return new SingleThreadedEvaluator<ProgramedTrafficNetIndividual>(ListUtils
				.buildList(objectiveFunctionWrapper));
	}

	/**
	 * Generates initial population with random binary individuals.
	 * 
	 * @param boaParams
	 * 
	 * @return List of binary individuals that will form the basis for first
	 *         iteration of the algorithm.
	 */
	private static List<ProgramedTrafficNetIndividual> generateListOfRandomProgramsNetIndividuals(
			EvolutionParams params) {

		List<ProgramedTrafficNetIndividual> result = new ArrayList<ProgramedTrafficNetIndividual>(
				params.N);

		Collection<Crossroad> crossRoads = Environment.getActiveEnvironment()
				.getAllCrossroads();

		for (int i = 0; i < params.N; i++) {
			List<Program> res = new ArrayList<Program>(crossRoads.size());
			for (Crossroad crossroad : crossRoads) {
				ProgramBuilder programBuilder = new ProgramBuilder(crossroad.getID());
				res.add(programBuilder.getProgramWithRandomizedPhasesDurations());
			}

			// we dont want to move that unnecessarily
			// since there is lot of other stuff to
			// prarametrize
			res.get(res.size() - 1).setPhaseShift(0L);

			result.add(new ProgramedTrafficNetIndividual(res.toArray(new Program[res
					.size()])));

		}

		params.n = result.get(0).size();

		return result;
	}

	public static StatsLoader runSimulation() throws Exception {
		final SimulatorConfiguration opt_conf = new OptSimConf(conf) {
			@Override
			public Boolean getDummyEnvironmentFlag() {
				return false;
			}
		};

		final Simulator sim = new Simulator(opt_conf);
		LOGGER.info("Running simulation, please wait");
		sim.run();
		return new StatsLoader(sim);
	}
}
